//////////////////////////////////////////////////
//  File       	: client.cpp                    //
//  Written by	: ludmiloff@gmail.com           //
//  Some copy/paste code from Antico project	//
//  Copyright  	: GPL2                          //
//////////////////////////////////////////////////

#include "client.h"
#include "decor.h"
#include "wmutils.h"
#include "dockbar.h"
#include "desktop.h"
#include "screen.h"
#include "adx.h"

extern Adx *deluxe;
extern Dockbar *dock;
extern Panel *toppanel;
extern Desktop *adesktop;


Client::Client(Window w, QWidget *parent)
    : QWidget(parent),
      clientId(w), titlebar(0), appName(""), clientName("")
{
	transientFor = groupLeader = clientLeader = None;

	keyboardGrabbed = false;
	dblClickMinimize = deluxe->minimizeDblClick;
	tbHeight = toppanel->height();
	resizeState = false;
	
	//setAutoFillBackground(false);
	//setAttribute(Qt::WA_NoSystemBackground);
	//setAttribute(Qt::WA_OpaquePaintEvent);
    //setAttribute(Qt::WA_TranslucentBackground);
	setAttribute(Qt::WA_DeleteOnClose);
	initialized = init();
}

Client::~Client()
{
	qDebug() << "CLIENT DESTRUCTOR";
    XFree(xsize);
    if (xwmhints) XFree(xwmhints);
	if (transientFor) {
		Client *c = deluxe->getClientFromWindow(transientFor);
		if (c) {
			c->transients.removeAll(clientId);
		}
	}
}

bool Client::init()
{
	XWindowAttributes attr;

	deluxe->grabServer();

	if (!XGetWindowAttributes(display(), clientId, &attr)) {
		qDebug() << "Cannot get window attributes for window WId: " << clientId;
		deluxe->ungrabServer();
		return false;
	}

	MyScreen *screen = deluxe->getScreenFromRoot(attr.root);
	if (!screen) {
		qDebug() << "Cannot determine screen info from window WId: " << clientId;
		deluxe->ungrabServer();
		return false;
	}

	// TODO: check for our own windows
	// TODO: check KDE system tray

	if (attr.override_redirect) {
		qDebug() << "Override redirect widnow WId: " << clientId;
		// TODO: compositorAddWindow
		deluxe->ungrabServer();
		return false;
	}

	screen_info = screen;
	// TODO: client_serial

	// TODO: Termination dialog pid

	getWindowName();
	getWindowHostName();
    getAppName();

	getTransientFor(screen->xroot, &transientFor);
	qDebug() << "TRANSIENT FOR" << transientFor;
	XChangeSaveSet(display(), clientId, SetModeInsert);

	// initialize structute
	xsize = NULL;
	flags = 0L;
	wm_flags = 0L;
	win_flags = FLAG_INITIAL;
	client_x = attr.x;
	client_y = attr.y;
	client_w = attr.width;
	client_h = attr.height;

	// TOOD: startup notification
	// TODO: xsync
    qDebug() << "get WM Normal hints";
	getWMNormalHints(false);

	xsize->x = client_x;
	xsize->y = client_y;
	xsize->width = client_w;
	xsize->height = client_h;
	// TODO: previousWidth, previousHeight
	border_w = attr.border_width;
	colorMap = attr.colormap;

	// TOOD: shaped

	if (((xsize->flags & (PMinSize | PMaxSize)) != (PMinSize | PMaxSize))
		|| (((xsize->flags & (PMinSize | PMaxSize)) == (PMinSize | PMaxSize))
			&& ((xsize->min_width < xsize->max_width)
				|| (xsize->min_height < xsize->max_height))))
	{
		FLAG_SET(win_flags, FLAG_IS_RESIZABLE);
	}

	// TODO: button status
	// TODO: XGetWMColormapWindows
	// TODO: fullscreen
	// TOOD: opacity
	// TODO: timeouts
	aclass.res_name = NULL;
	aclass.res_class = NULL;
	XGetClassHint(display(), clientId, &aclass);

    xwmhints = NULL;
    getWMHints();

	Utils::getWindowProperty(display(), clientId, Utils::WM_CLIENT_LEADER, &clientLeader);
	qDebug() << "CLIENT LEADER" << clientLeader;

	if (attr.map_state != IsUnmapped) {
		FLAG_SET(win_flags, FLAG_MAP_PENDING);
	}
	// TODO: other vars : ignore_unmap, type, type_atom
	// TODO: FLAG_SET ...

	getWMProtocols();
	// TODO: motif hints
	Utils::getHint(display(), clientId, Utils::WIN_HINTS, (long *) &win_hints);
	Utils::getHint(display(), clientId, Utils::WIN_STATE, (long *) &win_state);
	// TODO: getHint WIN_LAYER
	
	// TODO: user_time
	// TODO: PID

	// TODO: session
	// TODO: getWinState
	// TODO: getNetState

	// TODO: check getNetWMType == Atoms::windowType
	windowRole = Utils::windowType(clientId);
	if (windowRole == Utils::Dialog) {
		qDebug() << "DIALOG, TRANSIENT FOR " << transientFor;
	}
	// TODO: getInitialNetWMDesktop
	// TODO: getNetStruts
	// TODO: fullscreen for legacy apps

	// fixups
	if (windowRole == Utils::Dialog) {
		Client *parentClient;
		parentClient = deluxe->getClientFromWindow(transientFor);
		if (parentClient) {
			parentClient->transients.append(clientId);
		} else {
			Client *c1;
            foreach (c1, deluxe->clients) {
				qDebug() << "C1 " << groupLeader << transientFor;
				if (c1->clientId == clientId) continue;
				if ((c1->groupLeader == groupLeader) && ((c1->transientFor == None) || (c1->transientFor == screen_info->xroot))) {
					transientFor = c1->clientId;
					c1->transients.append(clientId);
					qDebug() << "FOUND GROUP LEADER AS MODAL PARENT";
					break;
				}
			}
		}
	}

	if (!FLAG_TEST(win_flags, FLAG_SESSION_MANAGED)) {
		// set initial position
		if (attr.map_state == IsUnmapped) {
			initPosition();
		}
	}

	// ****
	// old code goes here

	// reparent client with parent (qt)
	XSetWindowBorderWidth(display(), clientId, 0);  //client
	XSetWindowBorderWidth(display(), winId(), 0);	//parent
		
	XSetWindowAttributes at;
	at.event_mask = DECOR_EVENT_MASK;
	XChangeWindowAttributes(display(), winId(), CWEventMask, &at);
	at.event_mask = CLIENT_EVENT_MASK;
	XChangeWindowAttributes(display(), clientId, CWEventMask, &at);
		
	XGrabButton(display(), Button1, AnyModifier, clientId, FALSE,
			ButtonPressMask|ButtonReleaseMask, 
			GrabModeSync, GrabModeAsync, None, None);

	
	// get initial client geometry
	n_px = attr.x;
	n_py = attr.y;
	base_w = attr.width;
	base_h = attr.height;


	fixSizes();

    n_pw = client_w;// + DBL_BORDER_WIDTH;
    n_ph = client_h + TITLEBAR_HEIGHT + 8;
	

	getAppName();
	
	int bx, by;
	if (windowRole == Utils::Splash) {
		bx = by = 0;
		n_ph = n_ph - TITLEBAR_HEIGHT - 8;
	} else {
		bx = BORDER_WIDTH;
		by = TITLEBAR_HEIGHT;
	}

    XMoveResizeWindow(display(), clientId, bx, by, client_w, client_h);

    XReparentWindow(display(), winId(), rootWindow(), bx, by);
    XReparentWindow(display(), clientId, winId(), BORDER_WIDTH, Titlebar::getPreferedHeight(0));
    qDebug() << "Reparent window:" << clientId << "with Qt parent:" << winId();


	// move (in desktop center) and resize parent (qt)
	//n_px = (adesktop->width()-client_w)/2;
	//n_py = ((adesktop->height()-dock->height() - n_ph)*2)/3 + tbHeight;

	//qDebug() << "desktop h = " << adesktop->height() << "dock h = " << dock->height();
	//qDebug() << "n_px = " << n_px << " n_py = " << n_py << " client_w = " << client_w << " client_h = " << client_h;

	//move(n_px, n_py);
	move(client_x, client_y);
	resize(n_pw, n_ph);
	
	createDecor();
	
	// map client
	show();
	XMapWindow(display(), clientId);
	XSync(display(), false);
	
	maximized = false;
	clientState = NormalState;

	deluxe->ungrabServer();
	return true;
}


void Client::initPosition()
{
	qDebug() << "CLIENT INIT POSITION";
	Client *c2;
	bool place;
	bool position = (xsize->flags & (PPosition | USPosition));
	qDebug() << "POSITION=" << (position ? 1 : 0);

	if ((position) && (windowRole != Utils::Dialog)) // User specified psoition, leave
		return;

	if (windowRole == Utils::Dialog) {
		// Center transient relative to their parent window
		qDebug() << "PLACE Dialog WINDOW";
		c2 = deluxe->getClientFromWindow(transientFor);
		if (c2) {
			client_x = c2->client_x + (c2->client_w - client_w) / 2;
			client_y = c2->client_y + 40; // titlebar + menu + toolbar (rougly)
			if (client_y >= screen_info->height)
				client_y = (screen_info->height - client_h) / 3;
			place = false;
		} else
			place = true;
	} else {
		place = true;
	}

	if (place) {
		// move (in desktop center) and resize parent (qt)
		qDebug() << "PLACE NORMAL WINDOW";
		client_x = (screen_info->width - client_w)/2;
		int y1 = tbHeight + TITLEBAR_HEIGHT + 8;
		client_y = (screen_info->height - dock->height() - client_h - y1) / 3 + tbHeight;
	}
	qDebug() << "POS=" << client_x << client_y;
}

void Client::createDecor()
{
	qDebug() << "CREATE DECORATIONS FOR ROLE " << windowRole;
	if (windowRole == Utils::Splash) {
		titlebar = NULL;
		return;
	}
	
	layout = new QGridLayout(this);
	layout->setContentsMargins(0, 0, 0, 0);
	layout->setSpacing(0);

	// center frame where client apps is shown
	Border *center = new Border(this);
	layout->addWidget(center, 1, 1);

	titlebar = new Titlebar(appIcon, clientName, windowRole);
	layout->addWidget(titlebar, 0, 0, 1, -1);
	// Window buttons actions
	if (windowRole != Utils::Dialog) {
    	connect(qobject_cast <WinButton *>(titlebar->minBtn), SIGNAL(wMouseLeftPress()), SLOT(iconify()));
    	connect(qobject_cast <WinButton *>(titlebar->maxBtn), SIGNAL(wMouseLeftPress()), SLOT(maximize()));
    	connect(qobject_cast <WinButton *>(titlebar->closeBtn), SIGNAL(wMouseLeftPress()), SLOT(destroyClient()));
	}
	
	bottombar = new GenericBar();
	layout->addWidget(bottombar, 2, 0, 1, -1);

	// Titlebar actions
	connect(titlebar, SIGNAL(tMouseLeftPress(QMouseEvent *)), SLOT(titlebarMousePress(QMouseEvent *)));
	connect(titlebar, SIGNAL(tMouseMove(QMouseEvent *)), SLOT(titlebarMove(QMouseEvent *)));
	if (dblClickMinimize) {
		connect(titlebar, SIGNAL(tMouseDblClick()), SLOT(iconify()));
	}
	
	// Bottombar actions
	connect(bottombar, SIGNAL(tMouseLeftPress(QMouseEvent *)), SLOT(bottombarMousePress(QMouseEvent *)));
	connect(bottombar, SIGNAL(tMouseMove(QMouseEvent *)), SLOT(bottombarMove(QMouseEvent *)));
	connect(bottombar, SIGNAL(tMouseRelease()), SLOT(bottombarMouseRelease()));
	
	setLayout(layout);
}

void Client::setDblMinimizeClick(bool active)
{
	dblClickMinimize = active;
	disconnect(titlebar, SIGNAL(tMouseDblClick()), 0, 0);
	if (dblClickMinimize) {
		connect(titlebar, SIGNAL(tMouseDblClick()), SLOT(iconify()));
	}
}


void Client::fixSizes()
{	
	if (base_w >= adesktop->width())
		base_w = adesktop->width();

	if (adesktop->width() - base_w < 10)
		base_w = adesktop->width();
	
	if (base_h >= adesktop->height()-dock->height() - tbHeight - 8)
		base_h = adesktop->height()-dock->height() - tbHeight - 8;
	
	if (client_x <= adesktop->x())
		client_x = adesktop->x();
	if (client_y <= adesktop->y())
		client_y = adesktop->y();
}

void Client::setDecorState(bool s)
{
	if (!titlebar) return;
	if (s) {
		titlebar->changeState(Titlebar::borderActive);
		bottombar->changeState(Titlebar::borderActive);
	} else {
		titlebar->changeState(Titlebar::borderInactive);
		bottombar->changeState(Titlebar::borderInactive);		
	}
}

void Client::iconify()
{
	unmap();
	setClientState(IconicState);
	dock->addClient(this);  // add to dockbar
}

void Client::iconifyFast()
{
	unmap();
	clientState = IconicState;
	dock->addClient(this);  // add to dockbar
}

void Client::maximize()
{
	if (! maximized) {
        // save parent dimension
        n_px = x();
		n_py = y();
		n_pw = width();
		n_ph = height();
		// maximize parent with (0,0) vertex and screen dimension-dockbar height
		m_pw = adesktop->width();
		m_ph = adesktop->height()-dock->height();
		move(adesktop->x(), adesktop->y());
		resize(m_pw, m_ph);
		// set maximized state
		maximized = true;
	} else {
		// set last parent dimension
		move(n_px, n_py);
		resize(n_pw, n_ph);
		// set not maximized state
		maximized = false;
	}
	
	raise();
	XResizeWindow(display(), clientId, width()-DBL_BORDER_WIDTH, height()-TITLEBAR_HEIGHT-8);
}

void Client::removeFromDock()
{
	qDebug() << "REMOVE CLIENT FROM DOCK";
	dock->removeClient(this);
	map();
}

void Client::map()
{
	XUngrabKeyboard(display(), CurrentTime);
	XMapWindow(display(), clientId);
    XMapWindow(display(), winId());
    setClientState(NormalState);
	XGrabKeyboard(display(), clientId, TRUE, GrabModeAsync, GrabModeAsync, CurrentTime);
}
	
void Client::mapFast()
{
	XUngrabKeyboard(display(), CurrentTime);
    XMapWindow(display(), clientId);
    XMapWindow(display(), winId());
    clientState = NormalState;
	XGrabKeyboard(display(), clientId, TRUE, GrabModeAsync, GrabModeAsync, CurrentTime);
}

void Client::unmap()
{
    XUnmapWindow(display(), clientId);
    XUnmapWindow(display(), winId());
}

void Client::destroyClient()
{
	if (protDelete) {
		qDebug() << "ON DESTROY WINDOW (PROT DELETE)";
		Utils::sendWMProtocols(clientId, Utils::atom(Utils::WM_DELETE_WINDOW), CurrentTime);
	} else {
		qDebug() << "ON DESTROY WINDOW";
		XDestroyWindow(display(), clientId);
		XSync(display(), FALSE);
    }
}

//
// Window movement & resize routines
//

void Client::moveAt(const QPoint &p)
{
	client_x = p.x();
	client_y = p.y();
	move(client_x, client_y);
}


void Client::titlebarMousePress(QMouseEvent *event)
{
	mousepos = event->pos()+titlebar->pos();  // offset
}

void Client::titlebarMove(QMouseEvent *event)
{
	QPoint p(event->globalPos()-mousepos);
	if (p.x() <= adesktop->x())
		p.setX(adesktop->x());
	if (p.y() <= adesktop->y())
		p.setY(adesktop->y());
	moveAt(p);
}

void Client::bottombarMousePress(QMouseEvent *event)
{
	if (bottombar->cursorPos == GenericBar::RightPos)
		mousepos = event->globalPos();
	else if (bottombar->cursorPos == GenericBar::NonePos)
		mousepos = event->pos()+bottombar->pos();  // offset
	resizeState = true;
}

void Client::bottombarMouseRelease()
{
	resizeState = false;
}

void Client::bottombarMove(QMouseEvent *event)
{
	QPoint p(event->globalPos()-mousepos);

	if (bottombar->cursorPos == GenericBar::NonePos) {
		if (p.x() <= adesktop->x())
			p.setX(adesktop->x());
		if (p.y() <= adesktop->y())
			p.setY(adesktop->y());
		moveAt(p);

	} else if (bottombar->cursorPos == GenericBar::RightPos) {
		int resh = height()+p.y();
		int resw = width()+p.x();
		// FIXME: use minimal sizes supplied
		if (resw < 30) return;
		if (resh < 30) return;
		//move and resize parent
		//qDebug() << "RESIZE: " << p.x() << p.y() << resh << resw;
		XResizeWindow(display(), clientId, resw, resh-TITLEBAR_HEIGHT - 8);
		resize(resw, resh);
		mousepos = event->globalPos();
	}
}
